import java.rmi.RemoteException;
import java.rmi.server.RemoteServer;
import java.rmi.server.ServerNotActiveException;
import java.rmi.server.UnicastRemoteObject;
import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;


public class LockService extends UnicastRemoteObject implements LockServiceInterface {

	private static final String ALGORITHM = "AES";
	
	private static final int filelocked = 0;
	private static final int lockedby = 1;
	private static final int locktype = 2;
	
	String mysecretkey = "My super amazing secret LOCK key";

	String[][] locks;
	
	public LockService() throws RemoteException{
		locks = new String[0][];
	}

	/*DELETE THIS METHOD WHEN FINISHED DEBUGGING (whenever that'll be..)*/
	public void printLocks() throws RemoteException{
		System.out.println("Printing locks....");
		for(int i = 0; i < locks.length; i++){
			System.out.println(locks[i][filelocked] + " " + locks[i][lockedby] + " " + locks[i][locktype]);
		}
		System.out.println("...Finished printing locks");
	}
	
	//File path will need to include server address
	public boolean releaseRead(String filepath) throws RemoteException {
		String locktorelease = "r"; //r = read
		boolean res = false;
		int i;
		for(i = 0; i < locks.length; i++){
			try{
				if(locks[i][filelocked].equals(filepath) && locks[i][locktype].equals(locktorelease)
						&& locks[i][lockedby].equals(RemoteServer.getClientHost())){
					break;
				}
			}catch(Exception e){
				e.printStackTrace();
				return false;
			}
		}
		//Release the lock that matches the 
		if(i < locks.length){
			res = releaseLock(i);
		}
		return res;
	}
	
	public boolean releaseLocalRead(String filepath) throws RemoteException{
		try {
			return releaseRead(RemoteServer.getClientHost() + filepath);
		} catch (ServerNotActiveException e) {
			e.printStackTrace();
			return false;
		}		
	}

	//Filepath will need to include server address
	@Override
	public boolean requestRead(String filepath) throws RemoteException {
		String requestedlock = "r";
		boolean done = false;
		boolean lockavailable = true;
		//Can't grant read access if someone has write access
		for(int i = 0; i < locks.length; i++){
			if(locks[i][filelocked].equals(filepath)){
				//Handle the case the CLIENT already has the write lock
				try {
					if(locks[i][locktype].equals("w") && !locks[i][lockedby].equals(RemoteServer.getClientHost())){
						lockavailable = false;
						break;
					}
					//If client already has lock that it's requesting, just return true
					else if(locks[i][locktype].equals(requestedlock) && locks[i][lockedby].equals(RemoteServer.getClientHost())){
						return true;
					}
				} catch (ServerNotActiveException e) {
					e.printStackTrace();
				}
			}
		}
		if(lockavailable){
			return addLock(filepath, requestedlock);
		}
		else{
			return false;
		}
	}
	
	public boolean requestLocalRead(String filepath) throws RemoteException{
		try {
			return requestRead(RemoteServer.getClientHost() + filepath);
		} catch (ServerNotActiveException e) {
			e.printStackTrace();
			return false;
		}		
	}
	
	//Filepath will need to include server address
	@Override
	public boolean requestWrite(String filepath) throws RemoteException {
		String requestedlock = "w";
		boolean res = false;
		boolean lockavailable = true;
		//Can't grant write access if someone has any other lock on file
		for(int i = 0; i < locks.length; i++){
			if(locks[i][filelocked].equals(filepath)){
				try {
					//Handle the case where a client is requesting a lock it already has
					if(locks[i][locktype].equals(requestedlock) 
							&& locks[i][lockedby].equals(RemoteServer.getClientHost())){
						return true;
					}
				} catch (ServerNotActiveException e) {
					e.printStackTrace();
				}
				
				lockavailable = false;
				break;
			}
		}
		if(lockavailable){
			res = addLock(filepath, requestedlock);
		}
		return res;
	}
	
	public boolean requestLocalWrite(String filepath) throws RemoteException{
		try {
			return requestWrite(RemoteServer.getClientHost() + filepath);
		} catch (ServerNotActiveException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	//Filepath will need to include server address
	@Override
	public boolean releaseWrite(String filepath) throws RemoteException {
		String locktorelease = "w";
		boolean res = false;
		int i;
		for(i = 0; i < locks.length; i++){
			//If we have found the file's write lock
			if(locks[i][filelocked].equals(filepath) && locks[i][locktype].equals(locktorelease)){
				break;
			}
		}
		try{
			//If the client is the holder of this write lock
			if(i < locks.length && locks[i][lockedby].equals(RemoteServer.getClientHost())){
				res = releaseLock(i);
			}
		}catch(Exception e){
			e.printStackTrace();
			res = false;
		}
		return res;
	}
	
	public boolean releaseLocalWrite(String filepath) throws RemoteException{
		try {
			return releaseWrite(RemoteServer.getClientHost() + filepath);
		} catch (ServerNotActiveException e) {
			e.printStackTrace();
			return false;
		}		
	}
	
	//This is being passed with the client ip at the start
	private boolean addLock(String filepath, String permission){
		boolean res = true;
		
		String[][] updatedlocks;
		updatedlocks = new String[locks.length + 1][];
		//Copy current locks into this
		for(int i = 0; i < updatedlocks.length - 1; i++){
			updatedlocks[i] = new String[3];
			for(int j = 0; j < 3; j++){
				updatedlocks[i][j] = locks[i][j];
			}
			
		}
		updatedlocks[updatedlocks.length-1] = new String[3];
		updatedlocks[updatedlocks.length - 1][filelocked] = filepath;
		try {
			updatedlocks[updatedlocks.length - 1][lockedby] = RemoteServer.getClientHost();
		} catch (ServerNotActiveException e) {
			/*
			 * If we for some reason can't detect client IP, then
			 * we return false to client.. letting them know that
			 * we could not grant them the lock
			 */
			e.printStackTrace();
			res = false;
		}
		updatedlocks[updatedlocks.length - 1][locktype] = permission;
		locks = updatedlocks;
		return res;
	}
	
	private boolean releaseLock(int l){
		String[][] updatedlocks; //Where the new record of locks will be stored
		int pastl = 0;		//Records '1' if the loop below has gone past the lock to be removed
		updatedlocks = new String[locks.length - 1][];
		//Loops through all locks
		for(int i = 0; i < locks.length; i++){
			//Simply ensures that the lock to be released is not copied over
			if(i != l){
				//Loops through the lock properties
				for(int j = 0; j < 3; j++){
					updatedlocks[i - pastl][j] = locks[i][j];
				}
			}
			//If we're at the lock to be released
			else{
				pastl = 1;
			}
		}
		locks = updatedlocks;
		return true;
	}
	
	private String encrypt(String msg, byte[] keyValue) throws Exception{
		Key key = new SecretKeySpec(keyValue, ALGORITHM);
		Cipher c = Cipher.getInstance(ALGORITHM);
		c.init(Cipher.ENCRYPT_MODE, key);
		
		String valueToEnc = msg;
		byte[] encValue = c.doFinal(new BASE64Decoder().decodeBuffer(valueToEnc));
		String eValue = new BASE64Encoder().encode(encValue);
		return eValue;
	}
	
	private String decrypt(String msg, byte[] keyValue) throws Exception{
		Key key = new SecretKeySpec(keyValue, ALGORITHM);
		Cipher c = Cipher.getInstance(ALGORITHM);
		c.init(Cipher.DECRYPT_MODE, key);
		
		byte[] decordedValue = new BASE64Decoder().decodeBuffer(msg);
		byte[] decValue = c.doFinal(decordedValue);
		String dValue = new String(decValue);
		return dValue;
	}
}